package com.quesucede.gameoflife;

import android.content.Context;
import android.util.Log;

/**
 * The underlying implementation of the grid. Manages both the age of
 * each cell as well as the neighbors to determine the next generation.
 * @author Brett Kromkamp, William Sugarman, and Brian Pokrifka
 */
public class Life {

	/**
	 * Static variable used to designate pixel size of the cell
	 */
	public static final int CELL_SIZE = 8;
	
	/**
	 * Width of the grid (based on physical screen)
	 */
	public final int width;
	
	/**
	 * Height of the grid (based on physical screen)
	 */
	public final int height;

	/**
	 * A 2-dimensional array that contains the number of neighbors for some
	 * cell located at [y][x]
	 */
	private final int[][] _lifeGrid;
	
	/**
	 * The timeAliveGrid indicates, for each square, how many turns it's
	 * been on since it was turned on.  Off squares are 0.
	 */
	private final int[][] _timeAliveGrid;

	/**
	 * The current context used for obtaining preferences
	 */
	private Context _context;

	/**
	 * Initializes the Game of Life grid
	 * @param context - the context
	 * @param _width - scaled width
	 * @param _height - scaled height
	 */
	public Life(Context context, int _width, int _height) {
		this._context = context;
		this.width = _width / CELL_SIZE;
		this.height = _height / CELL_SIZE;
		this._lifeGrid = new int[height][width];
		this._timeAliveGrid = new int[height][width];
		initializeGrid();
	}

	/**
	 * Returns the grid of neighbors
	 * @return the underlying representation of the Game of Life
	 */
	public int[][] getGrid() {
		return _lifeGrid;
	}

	/**
	 * Returns the grid consisting of the cells' ages
	 * @return the grid of cell lifetimes
	 */
	public int[][] getTimeAliveGrid() {
		return _timeAliveGrid;
	}

	/**
	 * Initializes the grid by setting static cells to "on"
	 */
	public void initializeGrid() {
		resetGrid(_lifeGrid);
		resetGrid(_timeAliveGrid);

		_lifeGrid[8][(width / 2) - 1] = 1;
		_lifeGrid[8][(width / 2) + 1] = 1;
		_lifeGrid[9][(width / 2) - 1] = 1;
		_lifeGrid[9][(width / 2) + 1] = 1;
		_lifeGrid[10][(width / 2) - 1] = 1;
		_lifeGrid[10][(width / 2)] = 1;
		_lifeGrid[10][(width / 2) + 1] = 1;
	}

	/**
	 * Based on the preferences and the current number of neighbors,
	 * generates a new grid such that cells may be on or off as defined
	 * by the rules of the game of life
	 */
	public void generateNextGeneration() {
		int neighbours;
		int minimum = Integer.parseInt(SettingsFragment
				.getMinimumVariable(this._context));
		int maximum = Integer.parseInt(SettingsFragment
				.getMaximumVariable(this._context));
		int spawn = Integer.parseInt(SettingsFragment
				.getSpawnVariable(this._context));

		int[][] nextGenerationLifeGrid = new int[height][width];

		for (int h = 0; h < height; h++) {
			for (int w = 0; w < width; w++) {
				neighbours = calculateNeighbours(h, w);

				if (_lifeGrid[h][w] != 0) {
					if ((neighbours >= minimum) && (neighbours <= maximum)) {
						nextGenerationLifeGrid[h][w] = neighbours;
						//the square survived another turn, so its time alive goes up
						_timeAliveGrid[h][w] += 1;
					}
					else {
						//the square is turning off, so we set its time alive to 0
						_timeAliveGrid[h][w] = 0;
					}
				} else {
					if (neighbours == spawn) {
						nextGenerationLifeGrid[h][w] = spawn;
					}
				}
			}
		}
		copyGrid(nextGenerationLifeGrid, _lifeGrid);
	}
	
	/**
	 * Simply turns on cells off and off cells on
	 * @param x - the x-coordinate
	 * @param y - the y-coordinate
	 */
	public void toggleLife(int x, int y)
	{
		if( x >= 0 && x < this.width && y >= 0 && y < this.height ) {
			_lifeGrid[y][x] = (_lifeGrid[y][x] == 0 ? 1 : 0);
			_timeAliveGrid[y][x] = 0;
		}
		else {
			Log.e("toggling square", "square index was out of bounds, ignoring");
		}
	}

	/**
	 * Kills every cell
	 * @param grid - a grid representation of the Game of Life
	 */
	private void resetGrid(int[][] grid) {
		for (int h = 0; h < height; h++) {
			for (int w = 0; w < width; w++) {
				grid[h][w] = 0;
			}
		}
	}

	/**
	 * Calculates the number of neighbors around a cell
	 * @param y - the y-coordinate
	 * @param x - the x-coordinate
	 * @return the number of neighbors
	 */
	private int calculateNeighbours(int y, int x) {
		int total = (_lifeGrid[y][x] != 0) ? -1 : 0;
		for (int h = -1; h <= +1; h++) {
			for (int w = -1; w <= +1; w++) {
				if (_lifeGrid[(height + (y + h)) % height][(width + (x + w))
						% width] != 0) {
					total++;
				}
			}
		}
		return total;
	}

	/**
	 * Performs a deep copy of the array and outputs it
	 * into a consumed 2D array
	 * @param source - the array to be copied
	 * @param destination - the array to receive the copy
	 */
	private void copyGrid(int[][] source, int[][] destination) {
		for (int h = 0; h < height; h++) {
			for (int w = 0; w < width; w++) {
				destination[h][w] = source[h][w];
			}
		}
	}
}
